// renderer.cpp
//

#include "renderer.hpp"
#include "renderengine.hpp"
#include "node.hpp"



// RenderContext::RenderContext
//
RenderContext::RenderContext ( Renderer &renderer )
  : renderer(renderer)
{
}



// RenderContext::ubo_init
//
void RenderContext::ubo_init ( UBO &ubo,
                               const char *name,
                               GLuint binding )
{
  SDLayout &sd_layout = renderer.get_engine().get_sd_layout();
  const SDUniformComponent *comp = sd_layout.lookup_uniform_component(name);
  if (!comp)
    GLT_FATAL("uniform block not found: %s", name);

  GLT_TRACE("creating ubo '%s' (binding=%u, size=%lu)",
            name, binding, comp->type.size);

  ubo.binding = binding;
  gl.GenBuffers(1, &ubo.buffer);
  gl.BindBuffer(GL_UNIFORM_BUFFER, ubo.buffer);
  gl.BufferData(GL_UNIFORM_BUFFER, comp->type.size, NULL, /*[fixme]*/ GL_DYNAMIC_DRAW);
  gl.BindBuffer(GL_UNIFORM_BUFFER, 0);
  gl.BindBufferRange(GL_UNIFORM_BUFFER, binding, ubo.buffer, 0, comp->type.size);
}



// RenderContext::ubo_component_init
//
void RenderContext::ubo_component_init ( UBOComponent &ubo_component,
                                         const std::string &name )
{
  SDLayout &sd_layout = renderer.get_engine().get_sd_layout();
  const SDUniformComponent *comp = sd_layout.lookup_uniform_component(name);
  if (!comp)
    GLT_FATAL("uniform block not found: %s", name.c_str());

  ubo_component.offset = GLintptr(comp->offset);
}



// RenderContext::ubo_matrices_init
//
void RenderContext::ubo_matrices_init ()
{
  ubo_init(ubo_matrices, UBO_MATRICES_NAME, UBO_MATRICES_BINDING);
  ubo_component_init(ubo_matrices.projection, "Matrices.projection");
  ubo_component_init(ubo_matrices.model, "Matrices.model");
  ubo_component_init(ubo_matrices.normal, "Matrices.normal");

}



// RenderContext::ubo_lighting_init
//
void RenderContext::ubo_lighting_init ()
{
  ubo_init(ubo_lighting, UBO_LIGHTING_NAME, UBO_LIGHTING_BINDING);
  ubo_component_init(ubo_lighting.view_pos, "Lighting.view_pos");
  for (size_t l = 0; l < MAX_LIGHTS; l++)
    {
      std::string base_name = std::string{"Lighting.lights["} + std::to_string(l) + "]";
      ubo_component_init(ubo_lighting.lights[l].pos, base_name + ".pos");
      ubo_component_init(ubo_lighting.lights[l].ambient, base_name + ".ambient");
      ubo_component_init(ubo_lighting.lights[l].diffuse, base_name + ".diffuse");
      ubo_component_init(ubo_lighting.lights[l].specular, base_name + ".specular");
      ubo_component_init(ubo_lighting.lights[l].type, base_name + ".type");
    }
}



// RenderContext::ubo_material_init
//
void RenderContext::ubo_material_init ()
{
  ubo_init(ubo_material, UBO_MATERIAL_NAME, UBO_MATERIAL_BINDING);
  ubo_component_init(ubo_material.ambient, "Material.ambient");
  ubo_component_init(ubo_material.diffuse, "Material.diffuse");
  ubo_component_init(ubo_material.specular, "Material.specular");
  ubo_component_init(ubo_material.shininess, "Material.shininess");
}



// RenderContext::render_init
//
void RenderContext::render_init ()
{
  GLT_TRACE("render_context init");

  ubo_matrices_init();
  ubo_lighting_init();
  ubo_material_init();
}



// RenderContext::render_begin
//
void RenderContext::render_begin ()
{
  gl.Enable(GL_CULL_FACE);
  gl.CullFace(GL_BACK);
  
  current_group = -1;
  pipeline = nullptr;
  n_lights = 0;
  for (size_t l = 0; l < MAX_LIGHTS; l++)
    ubo_lighting.lights[l].type.set_int(ubo_lighting, 0);
}



// RenderContext::render_end
//
void RenderContext::render_end ()
{
  gl.Disable(GL_CULL_FACE);
  
  pipeline = nullptr;
}



// RenderContext::set_projection
//
void RenderContext::set_projection ( const glm::mat4 &m )
{
  ubo_matrices.projection.set_mat4(ubo_matrices, m);
}



// RenderContext::set_view_pos
//
void RenderContext::set_view_pos ( const glm::vec3 &p )
{
  ubo_lighting.view_pos.set_vec3(ubo_lighting, p);
}



// RenderContext::set_model_matrix
//
void RenderContext::set_model_matrix ( const glm::mat4 &m )
{
  ubo_matrices.model.set_mat4(ubo_matrices, m);
  if (pipeline && pipeline->normal_matrix())
    {
      glm::mat3 norm_mat = glm::transpose(glm::inverse(m));
      ubo_matrices.normal.set_mat3(ubo_matrices, norm_mat);
    }
}



// RenderContext::set_light
//
void RenderContext::set_light ( const wgl::Light &light )
{
  UBOLight &ubo_light = ubo_lighting.lights[n_lights++];
  n_lights = (n_lights % MAX_LIGHTS);

  ubo_light.type.set_int(ubo_lighting, WGL_ENUM_VALUE(light.get_type()));
  ubo_light.pos.set_vec3(ubo_lighting, light.get_position());
  ubo_light.ambient.set_vec3(ubo_lighting, light.get_ambient());
  ubo_light.diffuse.set_vec3(ubo_lighting, light.get_diffuse());
  ubo_light.specular.set_vec3(ubo_lighting, light.get_specular());
}



// RenderContext::set_material
//
void RenderContext::set_material ( const wgl::Material *material )
{
  if (material)
    {
      ubo_material.ambient.set_vec3(ubo_material, glm::vec3{material->ambient});
      ubo_material.diffuse.set_vec3(ubo_material, glm::vec3{material->diffuse});
      ubo_material.specular.set_vec3(ubo_material, glm::vec3{material->specular});
      ubo_material.shininess.set_float(ubo_material, material->shininess);
    }
  else
    {
      // [FIXME] reset values?
    }
}



// RenderContext::set_render_group
//
void RenderContext::set_render_group ( EntityGroupID group )
{
  GLT_ASSERT(group >= 0);
  
  if (group == current_group)
    return;
  current_group = group;

  if (!(pipeline = renderer.get_engine().get_group_pipeline(render_mode, group)))
    {
      GLT_WARNING("no pipeline found for group %d.%d", render_mode, group);
      // [FIXME] reset current pipeline?
      return;
    }

  pipeline->use();
}



// Renderer::Renderer
//
Renderer::Renderer ( RenderEngine &engine,
                     Node &scene )
  : engine(engine),
    scene(scene),
    render_context(*this)
{
  render_prepare();
}



// Renderer::render_prepare
//
void Renderer::render_prepare ()
{
  prepare_node(scene);
}



// Renderer::prepare_node
//
void Renderer::prepare_node ( Node &node )
{
  NodeData &data = node.get_data();
  data.render_prepare(scene_view);
  for (auto child: node)
    prepare_node(*child);
}



// Renderer::render_init
//
void Renderer::render_init ()
{
  GLT_TRACE("renderer init");

  render_context.render_init();
}



// Renderer::render
//
void Renderer::render ()
{
  // GLT_TRACE("render");

  // [TEMP] reset context
  gl.UseProgram(0);
  gl.Disable(GL_DEPTH_TEST);
  gl.BindTexture(GL_TEXTURE_2D, 0);
  gl.Disable(GL_TEXTURE_2D);
  gl.PolygonMode(GL_FRONT_AND_BACK, GL_LINE);

  gl.ClearColor(0.f, 0.f, 0.f, 1.f);
  gl.ClearDepth(1.f);
  gl.Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  render_context.render_begin();
  
  // render scene
  render_node(scene);

  render_context.render_end();
}



// Renderer::render_node
//
void Renderer::render_node ( Node &node )
{
  NodeData &data = node.get_data();
  
  // GLT_TRACE("render node: %p (data=%p)", &node, &data);

  scene_view.render(render_context);
  data.render(render_context);
  
  for (auto it: node)
    render_node(*it);
}
